/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.diagram.statemachine.command;

import java.util.Iterator;
import java.util.Vector;

import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.gef.RequestConstants;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.requests.ChangeBoundsRequest;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Pseudostate;
import org.eclipse.uml2.uml.Region;

import com.cea.papyrus.core.utils.Debug;
import com.cea.papyrus.diagram.statemachine.helper.NodeRegionHelper.Zone;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;
import com.cea.papyrus.diagraminterchange2.di2.Property;
import com.cea.papyrus.diagraminterchange2.di2.Uml1SemanticModelBridge;

/**
 * A command to specifically delete graph nodes attached to a region.
 *  
 * @author David Servat
 * @since 29 jan. 07
 */
public class DeleteRegionNodeCommand extends Command{

	/**
	 * Debug channel.
	 */
	private int channel = 501;

	/**
	 * The region to delete.
	 */
	private GraphNode region = null;

	/**
	 * Its zone.
	 */
	private String regionZone = null;

	/** Its drop location if any. */
	private String regionDropLocation = null;

	/** Its size and location. */
	private Dimension regionSize = null;
	private Point regionLocation = null;

	/**
	 * The splitter node underneath the region.
	 */
	private GraphNode splitter = null;

	/**
	 * The splitter node zone.
	 */
	private String splitterZone = null;

	/**
	 * The splitter node subnodes.
	 */
	private Vector<GraphNode> splitterSubnodes = null;

	/**
	 * The other node paired with the region to be deleted, it is either a region or a splitter.
	 */
	private GraphNode other = null;

	/**
	 * The other node zone.
	 */
	private String otherZone = null;

	/**
	 * The grand parent of the region to be deleted, i.e. parent of splitter.
	 */
	private GraphNode grandParent = null;

	/**
	 * A fake region resize command to be computed and executed before region deletion
	 * so that neighbouring nodes get scled accordingly.
	 */
	private ResizeRegionNodeCommand regionResizeCommand = null;

	/**
	 * Flag to handle the case of the deletion of the last region
	 */
	private boolean deleteLastRegion = false;
	
	/** 
	 * The constructor.
	 * 
	 * @param regionToDelete the graph node associated to the region to be deleted
	 */
	public DeleteRegionNodeCommand(GraphNode regionToDelete){
		Debug.debug(this, "call DeleteRegionNodeCommand(GraphNode regionToDelete)", channel);

		//a bunch of precautionary tests
		if(regionToDelete == null)
			throw new IllegalArgumentException();

		if(regionToDelete.getSemanticModel() == null) 
			throw new IllegalArgumentException();

		Element elt = ((Uml1SemanticModelBridge)regionToDelete.getSemanticModel()).getElement();
		if(elt == null)
			throw new IllegalArgumentException();

		if(!(elt instanceof Region))
			throw new IllegalArgumentException();

		//now we are sure to have received a correct graph node linked to a region
		//we store the infos
		region = regionToDelete;
	}

	/** 
	 * Calls the redo() method, hence the internaly computed regionResizeCommand is recomputed.
	 * This is to deal with the case when undo/redo have been performed on a region creation
	 * and the resize command may no longer be valid.
	 */
	@Override
	public void execute(){
		Debug.debug(this, "call DeleteRegionNodeCommand.execute()", channel);

		redo();
	}
	

	/**
	 * This methods does the job of region deletion.
	 * The idea here is to perform a fake resize command on the region to scale the neighbours appropriately,
	 * then update the zones properties of the various nodes involved,
	 * finally reconnect the nodes.
	 * Note: initializations have to take place just before execution of the command as there might have been 
	 * some changes in the underlying structure - e.g. when undo/redo region creation have been made 
	 */
	@Override
	public void redo(){
		Debug.debug(this, "call DeleteRegionNodeCommand.redo()", channel);

		//additional section to deal with deletion of last region
		Iterator it = region.getSemanticParent().getSemanticChild().iterator();
		int n = region.getSemanticParent().getSemanticChild().size();
		while(it.hasNext()){
			GraphNode gn = (GraphNode)it.next();
			//we need to remove the graph node attached to Entry/Exit points if any
			if((gn.getSemanticModel() != null) && (((Uml1SemanticModelBridge)gn.getSemanticModel()).getElement() instanceof Pseudostate))
				n--;
		}
		if(n == 1)
			deleteLastRegion = true;
		else
			deleteLastRegion = false;
		if(!deleteLastRegion){
			//a bunch of initializations
	
			//retrieve the region zone
			regionZone = ((Property)region.getProperty("zone")).getValue();
			
			//retrieve the region drop location
			regionDropLocation = ((Property)region.getProperty("dropLocation")).getValue();
			
			//retrieve its size
			regionSize = region.getSize().getCopy();
	
			//retrieve the splitter node
			splitter = (GraphNode)region.getContainer();
			//retrieve the zone property of splitter node
			splitterZone = ((Property)splitter.getProperty("zone")).getValue();
			//retrieve the splitterSubnodes of splitter
			splitterSubnodes = Zone.getAllSubNodesOfSplitter(splitter);
	
			//retrieve the other node contained in the splitter
			//try the first one
			other = (GraphNode)splitter.getContained().get(0);
			otherZone = ((Property)other.getProperty("zone")).getValue();
			if(otherZone.equals(regionZone)){
				//we chose the wrong one
				other = (GraphNode)splitter.getContained().get(1);
				otherZone = ((Property)other.getProperty("zone")).getValue();
			}
	
			//retrieve the parent of splitter node
			grandParent = (GraphNode)splitter.getContainer();
	
			//what we need to do is
	
			//change the size and locations
			//to do this we compute a fake resize request
			ChangeBoundsRequest request = new ChangeBoundsRequest();
			request.setType(RequestConstants.REQ_RESIZE);
			if(Zone.isRight(regionZone)){
				//make a full (-width) resize towards WEST
				request.setResizeDirection(PositionConstants.WEST);
				request.setSizeDelta(new Dimension(-regionSize.width, 0));
			}
			else if(Zone.isLeft(regionZone)){
				//make a full (-width) resize towards EAST
				request.setResizeDirection(PositionConstants.EAST);
				request.setSizeDelta(new Dimension(-regionSize.width, 0));
			}
			else if(Zone.isTop(regionZone)){
				//make a full (-height) resize towards SOUTH
				request.setResizeDirection(PositionConstants.SOUTH);
				request.setSizeDelta(new Dimension(0, -regionSize.height));
			}
			else if(Zone.isBottom(regionZone)){
				//make a full (-height) resize towards NORTH
				request.setResizeDirection(PositionConstants.NORTH);
				request.setSizeDelta(new Dimension(0, -regionSize.height));
			}
	
			//store a corresponding ResizeRegionNodeCommand
			regionResizeCommand = new ResizeRegionNodeCommand(region, request);
			//then execute it
			regionResizeCommand.execute();
	
			//retrieve the location and size after resize to store them
			regionLocation = region.getPosition().getCopy();
			regionSize = region.getSize().getCopy();
	
			//alter the zones of the splitterSubnodes 
			//i.e. keep only the last part of the zone id and change the prefix for that of splitter zone
			for(int i=0;i<splitterSubnodes.size();i++){
				GraphNode current = splitterSubnodes.get(i);
				String currentZone = ((Property)current.getProperty("zone")).getValue();
				String toKeep = currentZone.substring(otherZone.length());
				String newZoneId = splitterZone+toKeep;
				((Property)current.getProperty("zone")).setValue(newZoneId);
			}
	
			//reconnect the nodes correctly
			//order is important
			//remove old content then add new content
			//note the update on the semantic parent link
			splitter.setSemanticParent(null);
			region.setSemanticParent(null);
			grandParent.getContained().remove(splitter);
			grandParent.getContained().add(other);
		}
		//special case of deletion of last region of a composite state
		else{
			//retrieve the parent, here we should introduce a special variable parent for this because it is not the grand parent...
			grandParent = (GraphNode)region.getContainer();
			region.setSemanticParent(null);
			grandParent.getContained().remove(region);
		}
	}

	/** 
	 * This method starts by reconnectiong nodes as they were before the deletion,
	 * then performs an undo() on the internaly computed regionResizeCommand,
	 * finally the zones of the various nodes are updated.
	 */
	@Override
	public void undo(){
		Debug.debug(this, "call DeleteRegionNodeCommand.undo()", channel);

		if(!deleteLastRegion){
			//what we need to do is
	
			//reconnect the nodes as was previously
			//order is important
			//remove old content then add new content
			//note the update on the semantic parent link
			GraphNode semParent = (GraphNode)other.getSemanticParent();
			grandParent.getContained().remove(other);
			splitter.setSemanticParent(semParent);
			region.setSemanticParent(semParent);
			splitter.getContained().add(other);
			if(!region.getContainer().equals(splitter))
				splitter.getContained().add(region);
	
			//restore the size and location of region
			region.setSize(regionSize.getCopy());
			region.setPosition(regionLocation.getCopy());
			//now we can undo the ResizeRegionNodeCommand previously executed 
			regionResizeCommand.undo();
	
			//restore the zones of the splitterSubnodes except for the region
			//i.e. keep only the last part of the zone id and change the prefix for that of other zone
			for(int i=0;i<splitterSubnodes.size();i++){
				GraphNode current = splitterSubnodes.get(i);
				if(!current.equals(region)){
					String currentZone = ((Property)current.getProperty("zone")).getValue();
					if(splitterZone.length()>currentZone.length()){
						throw new IllegalArgumentException();
					}
					String toKeep = currentZone.substring(splitterZone.length());
					String newZoneId = otherZone+toKeep;
					((Property)current.getProperty("zone")).setValue(newZoneId);
				}
			}
			//restore the region zone
			((Property)region.getProperty("zone")).setValue(regionZone);
			//restore the region drop location
			((Property)region.getProperty("dropLocation")).setValue(regionDropLocation);
			grandParent.getContained().add(splitter);
		}
		//special case of deletion of last region
		else{
			region.setSemanticParent(grandParent);
			grandParent.getContained().add(region);
		}
			
	}
}
